package com.holystone.orca.util;

import org.apache.log4j.Logger;
import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SNMPTool {

	public static final int DEFAULT_VERSION = SnmpConstants.version2c;
	public static final String DEFAULT_PROTOCOL = "udp";
	public static final int DEFAULT_PORT = 161;
	public static final long DEFAULT_TIMEOUT = 2 * 1000L;
	public static final int DEFAULT_RETRY = 3;
	public static final String DEFAULT_COMMUNITY = "public";

	private static final Logger logger = Logger.getLogger(SNMPTool.class);

	/**
	 * 创建共同体对象communityTarget
	 * 
	 * @param address
	 * @param community
	 * @return CommunityTarget
	 */
	public static CommunityTarget createMyDefaultTarget(String address,
			String community) {
		Address targetAddress = GenericAddress.parse(address);
		CommunityTarget target = new CommunityTarget();
		target.setCommunity(new OctetString(community));
		target.setAddress(targetAddress);
		target.setVersion(DEFAULT_VERSION);
		target.setTimeout(DEFAULT_TIMEOUT);// milliseconds
		target.setRetries(DEFAULT_RETRY);
		return target;
	}

	/**
	 * 创建共同体对象communityTarget
	 * 
	 * @param targetAddress
	 * @param community
	 * @param version
	 * @return CommunityTarget
	 */
	public static CommunityTarget createCommunityTarget(Address targetAddress,
			String community, int version) {
		CommunityTarget target = new CommunityTarget();
		target.setCommunity(new OctetString(community));
		target.setAddress(targetAddress);
		target.setVersion(version);
		return target;
	}

	/**
	 * 创建共同体对象communityTarget
	 * 
	 * @param targetAddress
	 * @param community
	 * @param version
	 * @param timeOut
	 * @param retry
	 * @return CommunityTarget
	 */
	public static CommunityTarget createCommunityTarget(Address targetAddress,
			String community, int version, long timeOut, int retry) {
		CommunityTarget target = new CommunityTarget();
		target.setCommunity(new OctetString(community));
		target.setAddress(targetAddress);
		target.setVersion(version);
		target.setTimeout(timeOut); // milliseconds
		target.setRetries(retry);
		return target;
	}

	/**
	 * 创建共同体对象communityTarget
	 * 
	 * @param address
	 * @param community
	 * @param version
	 * @param timeOut
	 * @param retry
	 * @return CommunityTarget
	 */
	public static CommunityTarget createCommunityTarget(String address,
			String community, int version, long timeOut, int retry) {
		Address targetAddress = GenericAddress.parse(address);
		return createCommunityTarget(targetAddress, community, version,
				timeOut, retry);
	}

	/**
	 * 创建snmp Address
	 * 
	 * @param protocol
	 * @param ip
	 * @param port
	 * @return Address
	 */
	public static Address createAddress(String protocol, String ip, int port) {
		String address = protocol + ":" + ip + "/" + port;
		return GenericAddress.parse(address);
	}

	/**
	 * 创建snmp udp Address
	 * 
	 * @param ip
	 * @param port
	 * @return Address
	 */
	public static Address createUdpAddress(String ip, int port) {
		String address = ip + "/" + port;
		return new UdpAddress(address);
	}

	/**
	 * 创建snmp tcp Address
	 * 
	 * @param ip
	 * @param port
	 * @return Address
	 */
	public static TcpAddress createTcpAddress(String ip, int port) {
		String address = ip + "/" + port;
		return new TcpAddress(address);
	}

	/**
	 * 创建 UserTarget
	 * 
	 * @param targetAddress
	 * @param version
	 * @param timeOut
	 * @param level
	 * @param securityName
	 * @return UserTarget
	 */
	public static UserTarget createUserTarget(Address targetAddress,
			int version, long timeOut, int level, String securityName) {
		UserTarget target = new UserTarget();
		target.setAddress(targetAddress);
		target.setRetries(1);
		target.setTimeout(timeOut); // milliseconds
		target.setVersion(version);
		target.setSecurityLevel(level);
		target.setSecurityName(new OctetString(securityName));
		return target;
	}

	/**
	 * 创建 UserTarget
	 * 
	 * @param address
	 * @param version
	 * @param timeOut
	 * @param level
	 * @param securityName
	 * @return UserTarget
	 */
	public static UserTarget createUserTarget(String address, int version,
			long timeOut, int level, String securityName) {
		Address targetAddress = GenericAddress.parse(address);
		return createUserTarget(targetAddress, version, timeOut, level,
				securityName);
	}

	/**
	 * 去除字符串隐藏字符
	 * 
	 * @param target
	 */
	public static String trimChar(String target) {
		char[] handle = target.toCharArray();
		StringBuffer result = new StringBuffer();
		for (char c : handle) {
			if ((int) c > 31 && (int) c != 127) {
				result.append(String.valueOf(c));
			}
		}
		return result.toString();
	}

	/**
	 * 将16进制字符串转换为中文字
	 * 
	 * @param octetString
	 *            要转换的16进制
	 * @return 转换后的中文
	 */
	public static String getChinese(String octetString) { // snmp4j遇到中文直接转成16进制字符串
		try {
			if (octetString.contains(":")) {
				String[] temps = octetString.split(":");
				byte[] bs = new byte[temps.length];
				boolean isMatcher = true;
				Pattern pattern = Pattern.compile("^[0-9a-fA-F]{2}$");
				for (int i = 0; i < temps.length; i++) {
					Matcher matcher = pattern.matcher(temps[i]);
					boolean b = matcher.matches();
					if (b) {
						bs[i] = (byte) Integer.parseInt(temps[i], 16);
					} else {
						isMatcher = false;
					}
				}
				if (isMatcher) {
					String afterEcode = new String(bs, "gb2312");
					if (isMessyCode(afterEcode)) {
						afterEcode = new String(bs, "utf-8");
						if (isMessyCode(afterEcode)) {
							afterEcode = new String(bs, "unicode");
							if (isMessyCode(afterEcode)) {
								afterEcode = "编码错误";
							}
						}
					}
					return afterEcode;
				} else {
					return octetString;
				}
			} else {
				return octetString;
			}
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 解析日期和时间
	 * 
	 * @param value
	 *            要解析的16进制字符
	 * @return 解析后的时间字符串
	 */
	public static String parseDateAndTime(Variable value) {
		OctetString oct = (OctetString) value;
		byte[] bts = oct.getValue();
		byte[] format_str = new byte[128]; // 保存格式化过后的时间字符串
		int year = bts[0] * 256 + 256 + bts[1];
		int month = bts[2];
		int day = bts[3];
		int hour = bts[4];
		int minute = bts[5];
		int second = bts[6];
		// int msecond=bts[7];
		// 以下为格式化字符串
		int index = 3;
		int temp = year;
		for (; index >= 0; index--) {
			format_str[index] = (byte) (48 + (temp - temp / 10 * 10));
			temp /= 10;
		}

		format_str[4] = '-';
		index = 6;
		temp = month;
		for (; index >= 5; index--) {
			format_str[index] = (byte) (48 + (temp - temp / 10 * 10));
			temp /= 10;
		}

		format_str[7] = '-';
		index = 9;
		temp = day;
		for (; index >= 8; index--) {
			format_str[index] = (byte) (48 + (temp - temp / 10 * 10));
			temp /= 10;
		}

		format_str[10] = ' ';
		index = 12;
		temp = hour;
		for (; index >= 11; index--) {
			format_str[index] = (byte) (48 + (temp - temp / 10 * 10));
			temp /= 10;
		}

		format_str[13] = ':';
		index = 15;
		temp = minute;
		for (; index >= 14; index--) {
			format_str[index] = (byte) (48 + (temp - temp / 10 * 10));
			temp /= 10;
		}

		format_str[16] = ':';
		index = 18;
		temp = second;
		for (; index >= 17; index--) {
			format_str[index] = (byte) (48 + (temp - temp / 10 * 10));
			temp /= 10;
		}

		// format_str[19]='.';
		// index=21;
		// temp=msecond;
		// for(; index>=20; index--){
		// format_str[index]=(byte)(48+(temp-temp/10*10));
		// temp/=10;
		// }
		//
		// format_str[22]=0;

		return new String(format_str);
	}

	/**
	 * 判断字符是否是中文
	 *
	 * @param c
	 *            字符
	 * @return 是否是中文
	 */
	public static boolean isChinese(char c) {
		Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
		if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
				|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
				|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
				|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION
				|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
				|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) {
			return true;
		}
		return false;
	}

	/**
	 * 判断字符串是否是乱码
	 *
	 * @param strName
	 *            字符串
	 * @return 是否是乱码
	 */
	public static boolean isMessyCode(String strName) {
		Pattern p = Pattern.compile("\\s*|t*|r*|n*");
		Matcher m = p.matcher(strName);
		String after = m.replaceAll("");
		String temp = after.replaceAll("\\p{P}", "");
		char[] ch = temp.trim().toCharArray();
		float chLength = ch.length;
		float count = 0;
		for (int i = 0; i < ch.length; i++) {
			char c = ch[i];
			if (!Character.isLetterOrDigit(c)) {
				if (!isChinese(c)) {
					count = count + 1;
				}
			}
		}
		float result = count / chLength;
		if (result > 0.3) {
			return true;
		} else {
			return false;
		}

	}

	/**
	 * SNMP通过get方式获取OID对应的值
	 * 
	 * @param ipAddress
	 *            目标机IP
	 * @param port
	 *            端口
	 * @param community
	 *            共同体
	 * @param timeout
	 *            超时
	 * @param targetOid
	 *            目标oid
	 * @param encoding
	 *            编码
	 * @return 采集结果
	 */
	public static Map<String, String> snmpGet(String ipAddress, String port,
			String community, Long timeout, String[] targetOid, String encoding) {
		logger.debug("服务IP[" + ipAddress + "],端口port[" + port
				+ "],共同体community[" + community + "],OID["
				+ targetOid.toString() + "]get方式获取值");
		Map<String, String> oidValues = new TreeMap<String, String>();
		@SuppressWarnings("rawtypes")
		TransportMapping transport = null;
		Snmp snmp = null;
		try {
			// 地址是本机 端口一般都是161
			Address targetAddress = GenericAddress.parse(DEFAULT_PROTOCOL + ":"
					+ ipAddress + "/" + port);
			transport = new DefaultUdpTransportMapping();
			snmp = new Snmp(transport);
			transport.listen();
			// PDU 对象
			PDU pdu = new PDU();
			if (targetOid != null && 0 != targetOid.length) {
				for (String oid : targetOid) {
					pdu.add(new VariableBinding(new OID(oid)));
				}
			}
			// 操作类型
			pdu.setType(PDU.GET);
			if (timeout == null || timeout == 0) {
				timeout = DEFAULT_TIMEOUT;
			}
			CommunityTarget target = SNMPTool.createCommunityTarget(
					targetAddress, community, DEFAULT_VERSION, timeout, 3);
			ResponseEvent revent = snmp.send(pdu, target);
			if (revent != null && revent.getResponse() != null) {
				@SuppressWarnings("unchecked")
				Vector<VariableBinding> recVBs = (Vector<VariableBinding>) revent
						.getResponse().getVariableBindings();
				for (int i = 0; i < recVBs.size(); i++) {
					VariableBinding recVB = recVBs.elementAt(i);
					if (null != encoding && !"".equals(encoding)) {
						oidValues.put(recVB.getOid().toString(),
								trimChar(getChinese(recVB.getVariable()
										.toString())));
					} else {
						oidValues.put(recVB.getOid().toString(), trimChar(recVB
								.getVariable().toString()));
					}
				}
			}
		} catch (IOException e) {
			logger.debug("服务IP[" + ipAddress + "],端口port[" + port
					+ "],共同体community[" + community + "],OID["
					+ targetOid.toString() + "]get方式获取值error[" + e.getMessage()
					+ "]");
			e.printStackTrace();
		} finally {
			if (snmp != null) {
				try {
					snmp.close();
				} catch (IOException ex1) {
					snmp = null;
				}
			}
			if (transport != null) {
				try {
					transport.close();
				} catch (IOException ex2) {
					transport = null;
				}
			}
		}
		logger.debug("服务IP[" + ipAddress + "],端口port[" + port
				+ "],共同体community[" + community + "],OID["
				+ targetOid.toString() + "]get方式获取值成功[" + oidValues.size()
				+ "]");
		return oidValues;
	}

	/**
	 * 1)responsePDU == null<br>
	 * 2)responsePDU.getErrorStatus() != 0<br>
	 * 3)responsePDU.get(0).getOid() == null<br>
	 * 4)responsePDU.get(0).getOid().size() < targetOID.size()<br>
	 * 5)targetOID.leftMostCompare(targetOID.size(),responsePDU.get(0).getOid())
	 * !=0<br>
	 * 6)Null.isExceptionSyntax(responsePDU.get(0).getVariable().getSyntax())<br>
	 * 7)responsePDU.get(0).getOid().compareTo(targetOID) <= 0<br>
	 * 
	 * @param ipAddress
	 * @param community
	 * @param targetOid
	 * @param encoding
	 */
	public static Map<String, String> snmpWalk(String ipAddress, String port,
			String community, Long timeout, String targetOid, String encoding) {
		logger.debug("服务IP[" + ipAddress + "],端口port[" + port
				+ "],共同体community[" + community + "],OID["
				+ targetOid.toString() + "]walk方式获取值");
		Map<String, String> oidValueMap = new TreeMap<String, String>();
		String address = DEFAULT_PROTOCOL + ":" + ipAddress + "/" + port;
		OID targetOID = new OID(targetOid);

		PDU requestPDU = new PDU();
		requestPDU.setType(PDU.GETNEXT);
		requestPDU.add(new VariableBinding(targetOID));
		if (timeout == null || timeout == 0) {
			timeout = DEFAULT_TIMEOUT;
		}
		CommunityTarget target = SNMPTool.createCommunityTarget(address,
				community, DEFAULT_VERSION, timeout, 3);
		@SuppressWarnings("rawtypes")
		TransportMapping transport = null;
		Snmp snmp = null;
		try {
			transport = new DefaultUdpTransportMapping();
			snmp = new Snmp(transport);
			transport.listen();
			boolean finished = false;
			while (!finished) {
				VariableBinding vb = null;
				ResponseEvent response = snmp.send(requestPDU, target);
				PDU responsePDU = response.getResponse();

				if (null == responsePDU) {
					finished = true;
					break;
				} else {
					vb = responsePDU.get(0);
				}
				// check finish
				finished = checkWalkFinished(targetOID, responsePDU, vb);
				if (!finished) {
					if (encoding != null && !"".equals(encoding)) {
						oidValueMap
								.put(vb.getOid().toString(),
										trimChar(getChinese(vb.getVariable()
												.toString())));
					} else {
						oidValueMap.put(vb.getOid().toString(), trimChar(vb
								.getVariable().toString()));
					}
					requestPDU.setRequestID(new Integer32(0));
					requestPDU.set(0, vb);
				}
			}
		} catch (Exception e) {
			logger.debug("服务IP[" + ipAddress + "],端口port[" + port
					+ "],共同体community[" + community + "],OID["
					+ targetOid.toString() + "]walk方式获取值");
		} finally {
			if (snmp != null) {
				try {
					snmp.close();
				} catch (IOException ex1) {
					snmp = null;
				}
			}
			if (transport != null) {
				try {
					transport.close();
				} catch (IOException ex2) {
					transport = null;
				}
			}
		}
		logger.debug("服务IP[" + ipAddress + "],端口port[" + port
				+ "],共同体community[" + community + "],OID["
				+ targetOid.toString() + "]walk方式获取值成功[" + oidValueMap.size()
				+ "]");
		return oidValueMap;
	}

	/**
	 * check snmp walk finish
	 * 
	 * @param targetOID
	 * @param responsePDU
	 * @param vb
	 * @return
	 */
	public static boolean checkWalkFinished(OID targetOID, PDU responsePDU,
			VariableBinding vb) {
		boolean finished = false;
		if (responsePDU.getErrorStatus() != 0) {
			finished = true;
		} else if (vb.getOid() == null) {
			finished = true;
		} else if (vb.getOid().size() < targetOID.size()) {
			finished = true;
		} else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
			finished = true;
		} else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
			finished = true;
		} else if (vb.getOid().compareTo(targetOID) <= 0) {
			finished = true;
		}
		return finished;
	}
	/**
	 * 采集流量
	 * @author: 李海宁
	 * @date: 2016年7月12日 下午3:15:30
	 * @param ip 目标机ip
	 * @param port snmp端口
	 * @param octerString 共同体
	 * @param flowOidGroup oid集合
	 */
	public static Map<String,String> getFlow(String ip,String port,String octerString,int version, long interval,List<String> flowOidGroup){
		Map<String,String> map = new HashMap<String,String>();
		Snmp snmp = null;
		TransportMapping transport = null;
		try {
			transport = new DefaultUdpTransportMapping();
			Address targetAddress = GenericAddress.parse("udp:" + ip + "/"+port);
			snmp = new Snmp(transport);
			transport.listen();// 监听
			CommunityTarget target = new CommunityTarget();
			target.setCommunity(new OctetString(octerString));// 设置共同体名
			target.setAddress(targetAddress);// 设置目标Agent地址
			target.setRetries(2);// 重试次数
			target.setTimeout(3000);// 超时设置
			target.setVersion(1);// 版本
			PDU request = new PDU();
			request.setType(PDU.GET);// 操作类型GET
			request.add(new VariableBinding(new OID(".1.3.6.1.2.1.1.3.0")));//时间OID，所有设备一样
			for (String FlowOid : flowOidGroup) {
				request.add(new VariableBinding(new OID(FlowOid)));
			}
			//System.out.println("4-------"+System.currentTimeMillis());
			// 取两次数据，间隔n秒，算差值
			long[] time = new long[2];
			long[][] flow = new long[2][flowOidGroup.size()];
			for (int count = 0; count < 2; count++) {
				ResponseEvent respEvt = snmp.send(request, target);// 发送请求
				if (respEvt != null && respEvt.getResponse() != null) {
					// 从目的设备取值，得到Vector
					Vector<VariableBinding> revBindings = (Vector<VariableBinding>) respEvt.getResponse().getVariableBindings();
					String TimeTicks = revBindings.elementAt(0).getVariable().toString().trim();
					String[] TimeString = TimeTicks.split(" ");// 得到时间字符串数组
					// 取时间 186 days, 21:26:15.24，也有可能没有day，就是不到一天
					if (TimeTicks.contains("day")) {
						time[count] = Long.parseLong(TimeString[0])
								* 24
								* 3600
								+ Long.parseLong(TimeString[2].split(":")[0])
								* 3600
								+ Long.parseLong(TimeString[2].split(":")[1])
								* 60
								+ Math.round(Double.parseDouble(TimeString[2]
										.split(":")[2]));
					} else {
						time[count] = Long
								.parseLong(TimeString[0].split(":")[0])
								* 3600
								+ Long.parseLong(TimeString[0].split(":")[1])
								* 60
								+ Math.round(Double.parseDouble(TimeString[0]
										.split(":")[2]));
					}
					// 取端口流量
					for (int i = 0; i < flowOidGroup.size(); i++) {
						flow[count][i] = Long.parseLong(revBindings.elementAt(i + 1).getVariable().toString());
					}
				}
				if (count == 0){
					Thread.sleep(interval);// 延时5秒后，第二次取值
				}
			}
			//System.out.println("5-------"+System.currentTimeMillis());
			// 计算并为时间和最终流量赋值
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
			
			String nowTime = sdf.format(new Date());// 当前时间
			
			for (int i = 0; i < flowOidGroup.size(); i++) {
				long sub = flow[1][i] - flow[0][i];
				/*
				 * 端口流量值为无符号32位，超出后就归0，所以如果两次取值差值为负，
				 * 必然出现一次归0的情况，由于单个端口的流量不可能超过每5秒1*2^32字节
				 */
				if (sub < 0) {
					// 因为端口流量为无符号32位，所以最大值是有符号32位的两倍
					sub += 2L * Integer.MAX_VALUE;
				}
				map.put("time", new Date().getTime()+"");
				map.put("value", sub+"");
			}
			//System.out.println("6-------"+System.currentTimeMillis());
		} catch (IOException | InterruptedException e) {
			e.printStackTrace();
		} finally{
			try {
				if(snmp!=null){
						snmp.close();
				}
				if(transport!=null){
					transport.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return map;
	}
	public static void main(String[] args) throws Exception {
		while (true) {
			List<String> flowOidGroup=new ArrayList<String>();
			flowOidGroup.add("1.3.6.1.2.1.2.2.1.10.5");
			Map<String, String> map = getFlow("192.168.20.65", "161", "public", 1, 1000, flowOidGroup);
			System.out.println(map);
			Thread.sleep(1000);
		}
	}
}
